---
title: Bun SPA
description: Quick guide to initializing and deploying a Bun-based React application to Cloudflare Workers using Alchemy.
sidebar:
  order: 1.6
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

This guide shows how to initialize and deploy a Bun-based React TypeScript application to Cloudflare using Alchemy. BunSPA provides a full-stack development experience with Bun's frontend tooling and Cloudflare Workers for the backend.

## Init

You have two options to get started:

### Option 1: Use Alchemy Create (Recommended)

Start by creating a new Bun SPA project with Alchemy:

<Tabs syncKey="pkgManager">
  <TabItem label="bun">
    ```sh
    bunx alchemy create my-bun-app --template=bun-spa
    cd my-bun-app
    ```
  </TabItem>
  <TabItem label="npm">
    ```sh
    npx alchemy create my-bun-app --template=bun-spa
    cd my-bun-app
    ```
  </TabItem>
  <TabItem label="pnpm">
    ```sh
    pnpm dlx alchemy create my-bun-app --template=bun-spa
    cd my-bun-app
    ```
  </TabItem>
  <TabItem label="yarn">
    ```sh
    yarn dlx alchemy create my-bun-app --template=bun-spa
    cd my-bun-app
    ```
  </TabItem>
</Tabs>

### Option 2: Add Alchemy to an Existing Bun Project

If you've already initialized a Bun project with `bun init` (which supports Tailwind CSS, shadcn/ui, and other templates), you can add Alchemy to it using `alchemy init`:

```sh
# If you haven't already, initialize your Bun project
bun init

# Add Alchemy to the existing project
bun alchemy init --framework bun-spa
```

The `alchemy init` command will:
- Create an `alchemy.run.ts` file with BunSPA configuration
- Validate or create `bunfig.toml` with required `env='BUN_PUBLIC_*'` configuration
- Add Alchemy scripts to your `package.json` (`deploy`, `destroy`, `alchemy:dev`)
- Install Alchemy as a dev dependency

:::note
The init command adds `alchemy:dev` (not `dev`) to preserve your existing dev script. You can rename it to `dev` if you prefer, or keep both.
:::

After initialization, update the paths in `alchemy.run.ts` to match your project structure:

```typescript
export const bunsite = await BunSPA("website", {
  frontend: "src/index.html", // adjust to match your HTML entrypoint(s)
  entrypoint: "src/server.ts", // adjust to match your backend API entrypoint
});
```

:::tip
Using `bun init` first gives you access to additional templates and configurations like Tailwind CSS and shadcn/ui support.
:::


## Log in to Cloudflare

Authenticate once with your Cloudflare account:

<Tabs syncKey="pkgManager">
  <TabItem label="bun">
    ```sh
    bun alchemy login
    ```
  </TabItem>
  <TabItem label="npm">
    ```sh
    npx alchemy login
    ```
  </TabItem>
  <TabItem label="pnpm">
    ```sh
    pnpm alchemy login
    ```
  </TabItem>
  <TabItem label="yarn">
    ```sh
    yarn alchemy login
    ```
  </TabItem>
</Tabs>

:::tip
Alchemy login creates Cloudflare OAuth tokens for alchemy. See the [Cloudflare](/guides/cloudflare) guide for other options.
:::

## Deploy

Run the deploy script generated by the template:

<Tabs syncKey="pkgManager">
  <TabItem label="bun">
    ```sh
    bun run deploy
    ```
  </TabItem>
  <TabItem label="npm">
    ```sh
    npm run deploy
    ```
  </TabItem>
  <TabItem label="pnpm">
    ```sh
    pnpm run deploy
    ```
  </TabItem>
  <TabItem label="yarn">
    ```sh
    yarn run deploy
    ```
  </TabItem>
</Tabs>

You'll get the live URLs of your application:

```sh
{
  url: "https://website.<your-account>.workers.dev",
  apiUrl: "https://website.<your-account>.workers.dev"
}
```

## Local Development

Work locally using the dev script:

<Tabs syncKey="pkgManager">
  <TabItem label="bun">
    ```sh
    bun run dev
    ```
  </TabItem>
  <TabItem label="npm">
    ```sh
    npm run dev
    ```
  </TabItem>
  <TabItem label="pnpm">
    ```sh
    pnpm run dev
    ```
  </TabItem>
  <TabItem label="yarn">
    ```sh
    yarn run dev
    ```
  </TabItem>
</Tabs>

This starts both Bun's dev server for the frontend (with hot module reloading) and Miniflare for the backend API.

## Destroy

Clean up all Cloudflare resources created by this stack:

<Tabs syncKey="pkgManager">
  <TabItem label="bun">
    ```sh
    bun run destroy
    ```
  </TabItem>
  <TabItem label="npm">
    ```sh
    npm run destroy
    ```
  </TabItem>
  <TabItem label="pnpm">
    ```sh
    pnpm run destroy
    ```
  </TabItem>
  <TabItem label="yarn">
    ```sh
    yarn run destroy
    ```
  </TabItem>
</Tabs>

## What files are created

### `.env`

Alchemy requires a locally set password to encrypt Secrets that are stored in state. Be sure to change this.

:::note
See the [Secret](/concepts/secret) documentation to learn more.
:::

```
ALCHEMY_PASSWORD=change-me
```

### `alchemy.run.ts`

`alchemy.run.ts` is your infrastructure as code with Alchemy.
Alchemy commands such as `alchemy dev` and `alchemy deploy` use `alchemy.run.ts` as their entrypoint.
Import types from `alchemy.run.ts` into your application code to get runtime types for Cloudflare Bindings.

### `bunfig.toml`

BunSPA requires a `bunfig.toml` file to expose `BUN_PUBLIC_*` environment variables to the frontend during development:

```toml
[serve.static]
env='BUN_PUBLIC_*'
```

:::tip
This configuration allows Bun to inline environment variables prefixed with `BUN_PUBLIC_` into your frontend code, making them accessible in the browser.
:::


### `tsconfig.json`

`tsconfig.json` is created including `alchemy.run.ts` and registering `@cloudflare/workers-types` and `bun-env.d.ts` globally


## How it works

BunSPA provides a full-stack development experience:

- **Frontend:** Bun's native dev server serves your HTML entrypoints with hot module reloading
- **Backend:** Miniflare runs your Cloudflare Worker locally with access to bindings (KV, D1, R2, etc.)
- **Deployment:** Both are deployed together to Cloudflare Workers with static assets

### Multiple HTML files

You can serve multiple HTML pages by passing an array to the `frontend` property:

```typescript
const bunsite = await BunSPA("website", {
  frontend: ["src/index.html", "src/about.html"],
  entrypoint: "src/worker.ts",
});
```

### Accessing the backend from the frontend

Use the `getBackendUrl` utility to reliably connect to your backend API:

```typescript
import { getBackendUrl } from "alchemy/cloudflare/bun-spa";

const apiBaseUrl = getBackendUrl();

// Make API requests in your frontent
fetch(new URL('api/request/path', apiBaseUrl))
  .then(res => res.json())
  .then(data => console.log(data));
```

This utility automatically uses `BUN_PUBLIC_BACKEND_URL` in development (injected by Alchemy) and falls back to the current origin in production, allowing your frontend to communicate seamlessly with your backend in both environments.

### Hot Module Replacement

BunSPA includes [Hot Module Replacement (HMR)](https://bun.com/docs/bundler/hmr) for instant updates during development. Add this to your main entry file:

```typescript
// src/main.tsx
if (import.meta.hot) {
  import.meta.hot.accept();
}
```

This tells Bun that your module can be hot-replaced, preserving application state when you save changes to your frontend files.

